package xyz.zyt123.hikvideo.bean;

import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.FFmpegLogCallback;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.FrameRecorder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;

/**
 * @author zhuyuntao
 **/
public class CameraPush {
  private static final Logger log = LoggerFactory.getLogger(CameraPush.class);
  protected FFmpegFrameGrabber grabber = null;// 解码器
  protected FFmpegFrameRecorder record = null;// 编码器
  int width;// 视频像素宽
  int height;// 视频像素高

  // 视频参数
  protected int audiocodecid;
  protected int codecid;
  protected double framerate;// 帧率
  protected int bitrate;// 比特率

  // 音频参数
  // 想要录制音频，这三个参数必须有：audioChannels > 0 && audioBitrate > 0 && sampleRate > 0
  private int audioChannels;
  private int audioBitrate;

  // 设备信息
  private final Camera camera;

  public CameraPush(Camera camera) {
    this.camera = camera;
  }

  /**
   * 选择视频源
   *
   * @author wuguodong
   */
  public CameraPush from() throws Exception {
    // 采集/抓取器
    grabber = new FFmpegFrameGrabber(camera.getRtsp());
    if (camera.getRtsp().contains("rtsp")) {
      grabber.setOption("rtsp_transport", "tcp");// tcp用于解决丢包问题
    }
    // 设置采集器构造超时时间
    grabber.setOption("stimeout", "2000000");
    grabber.start();// 开始之后ffmpeg会采集视频信息，之后就可以获取音视频信息
    width = grabber.getImageWidth();
    height = grabber.getImageHeight();
    // 若视频像素值为0，说明采集器构造超时，程序结束
    if (width == 0 && height == 0) {
      System.err.println("[ERROR]   拉流超时...");
      return null;
    }
    // 视频参数
    audiocodecid = grabber.getAudioCodec();
    System.err.println("音频编码：" + audiocodecid);
    codecid = grabber.getVideoCodec();
    framerate = grabber.getVideoFrameRate();// 帧率
    bitrate = grabber.getVideoBitrate();// 比特率
    // 音频参数
    // 想要录制音频，这三个参数必须有：audioChannels > 0 && audioBitrate > 0 && sampleRate > 0
    audioChannels = grabber.getAudioChannels();
    audioBitrate = grabber.getAudioBitrate();
    if (audioBitrate < 1) {
      audioBitrate = 128 * 1000;// 默认音频比特率
    }
    return this;
  }

  /**
   * 选择输出
   *
   * @author zhuyuntao
   */
  public CameraPush to() throws Exception {
    //设置FFmpeg日志级别
    avutil.av_log_set_level(avutil.AV_LOG_ERROR);
    FFmpegLogCallback.set();
    File file = new File(camera.getOutput());
    file.getParentFile().deleteOnExit();
    boolean mkdir = file.getParentFile().mkdirs();
    log.info("创建:{}", mkdir);
    // 录制/推流器
    record = new FFmpegFrameRecorder(camera.getOutput(), width, height, audioChannels);
    record.setVideoOption("crf", "28");// 画面质量参数，0~51；18~28是一个合理范围
    record.setVideoCodec(avcodec.AV_CODEC_ID_H264);
    record.setGopSize(2);
    record.setFrameRate(framerate);
    record.setVideoBitrate(bitrate);
    record.setFormat("hls");

    // 设置HLS切片参数
    //将每个切片时长设置为10秒
    record.setOption("hls_time", "10");
    //设置切片数大小10
    record.setOption("hls_list_size", "10");
    //设置切片循环次数为1000
    record.setOption("hls_wrap", "10");
    //每次切片完成后，都会删除之前的切片文件。如果不设置或设置为其他值，则不会删除之前的切片文件。
    record.setOption("hls_flags", "delete_segments");
    //在使用 H.264 编码时，通常要求输入的像素格式为 YUV420P。如果输入的像素格式不匹配，就可能导致 avcodec_send_frame() 错误
    record.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);

    //CRF 是一种用于控制视频/音频质量的参数，它允许在保持目标质量的同时动态地调整比特率。较低的CRF值表示更高的质量，但也可能导致较大的文件大小
    record.setAudioOption("crf", "23");
    record.setAudioChannels(audioChannels);
    record.setAudioBitrate(audioBitrate);
    record.setAudioCodec(avcodec.AV_CODEC_ID_AAC);

    record.start();
    return this;
  }

  /**
   * 转封装
   *
   * @author zhuyuntao
   */
  public void go() throws org.bytedeco.javacv.FrameGrabber.Exception, org.bytedeco.javacv.FrameRecorder.Exception {
    // 连续五次没有采集到帧则认为视频采集结束，程序错误次数超过5次即中断程序
    // 将探测时留下的数据帧释放掉，以免因为dts，pts的问题对推流造成影响
    grabber.flush();
    for (int err_index = 0; err_index < 5; ) {
      try {
        // 用于中断线程时，结束该循环
        Thread.sleep(1);
        Frame frame;
        // 获取没有解码的音视频帧
        if ((frame = grabber.grab()) != null) {
          record.record(frame);
        }
        // 不需要编码直接把音视频帧推出去
      } catch (InterruptedException e) {
        // 当需要结束推流时，调用线程中断方法，中断推流的线程。当前线程for循环执行到
        // nowThread.sleep(1);这行代码时，因为线程已经不存在了，所以会捕获异常，结束for循环
        // 销毁构造器
        grabber.close();
        record.close();
        System.err.println("设备中断推流成功...");
        break;
      } catch (FrameGrabber.Exception | FrameRecorder.Exception e) {
        log.error(e.getMessage());
        err_index++;
      }
    }
    // 程序正常结束销毁构造器
    grabber.close();
    record.close();
    System.err.println("设备推流完毕...");
  }
}
